#include <sxOpenGL_DefaultPlotAxesRenderer.h>
#include <sxOpenGL.h>
#include <gl/glut.h>

Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::OpenGL_DefaultPlotAxesRenderer(const std::shared_ptr<OpenGL_Context>& graphicsContext) : GraphicsObjectRenderer(graphicsContext) {
	this->majorColor = Color4f(0.349f, 0.349, 0.349f, 1.0f);
	this->minorColor = Color4f(0.259f, 0.259f, 0.259f, 1.0f);
}

Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::~OpenGL_DefaultPlotAxesRenderer() {}

/*
 * PlotAxes are considered 'static' objects therefore a VBO can be used and
 * a note can be made that the data should not change frequently. All of the
 * structures required by the PlotAxes should be constructed. Components of the
 * PlotAxes that are toggled to be hidden are simply not rendered.
 */
bool Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::construct(OpenGL_Context* const graphicsContext, PlotAxes* const plot, PlotAxes_Representation* const physicalRepresentation) {
	//--------------------------------------------------------------------------
	// Initialize VBO and fill in the data
	//--------------------------------------------------------------------------
	return true;
}

/* 
 * PlotAxes are considered 'static' therefore there shouldn't be a need for
 * this update function in this renderer.
 */
void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::update(OpenGL_Context* const graphicsContext, PlotAxes* const plot, float dt) {
	return;
}

void DrawTextAtPoint(const Sx::String& text, const Sx::Graphics::Color4f& color, float x, float y, float z) {
	glColor4f(color.getR(), color.getG(), color.getB(), color.getA());
	glRasterPos3f(x, y, 0.1f);
	for ( unsigned int i = 0; i < text.length(); i++ )
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, text.at(i));
}

inline void IdentifyMajorLines(unsigned int index, unsigned int major, Sx::Graphics::Color4f& majorColor, Sx::Graphics::Color4f& minorColor, unsigned int nuTicks, unsigned int puTicks, unsigned int nvTicks, unsigned int pvTicks) {
	unsigned int nuLinesStartIndex = 0;
	unsigned int puLinesStartIndex = nuTicks;
	unsigned int nvLinesStartIndex = nuTicks + puTicks;
	unsigned int pvLinesStartIndex = nuTicks + puTicks + nvTicks;

	if ( index >= 0 && index < puLinesStartIndex ) {
		if ( (index+1) % major == 0 ) glColor4fv(majorColor);
		else glColor4fv(minorColor);
	}
	else if ( index >= puLinesStartIndex && index < nvLinesStartIndex ) {
		int adjIndex = ((index+1) - puLinesStartIndex);
		if ( adjIndex % major == 0 ) glColor4fv(majorColor);
		else glColor4fv(minorColor);
	}
	else if ( index >= nvLinesStartIndex && index < pvLinesStartIndex ) {
		int adjIndex = ((index+1) - nvLinesStartIndex);
		if ( adjIndex % major == 0 ) glColor4fv(majorColor);
		else glColor4fv(minorColor);
	}
	else {
		int adjIndex = ((index+1) - pvLinesStartIndex);
		if ( adjIndex % major == 0 ) glColor4fv(majorColor);
		else glColor4fv(minorColor);
	}
}

inline bool DrawAxes(Sx::Graphics::Objects::PlotAxes* plot, Sx::Graphics::Representations::Objects::PlotAxes_Representation* physicalRepresentation) {
	if ( plot == nullptr ) return false;
	if ( physicalRepresentation == nullptr ) return false;

	bool x = plot->xAxisVisible();
	bool y = plot->yAxisVisible();
	bool z = plot->zAxisVisible();
		
	if ( x || y || z ) {
		glColor3f(0.0f, 0.0f, 0.0f);
		glBegin(GL_LINES);
			if ( x ) {
				glVertex3fv(physicalRepresentation->nx.data());
				glVertex3fv(physicalRepresentation->px.data());
			}

			if ( y ) {
				glVertex3fv(physicalRepresentation->ny.data());
				glVertex3fv(physicalRepresentation->py.data());
			}

			if ( z ) {
				glVertex3fv(physicalRepresentation->nz.data());
				glVertex3fv(physicalRepresentation->pz.data());
			}
		glEnd();
	}

	return true;
}

inline bool DrawAxisLabels(Sx::Graphics::Objects::PlotAxes* plot, Sx::Graphics::Color4f& color, unsigned int ticks, Sx::Math::Axis axis, const Sx::String& units) {
	if ( plot == nullptr ) return false;

	float curPos = 0.0f;
	float spacing = plot->getTickSpacing();
	int coef = 1;
	if ( axis == Sx::Math::NX || axis == Sx::Math::NY || axis == Sx::Math::NZ ) coef = -1;

	for ( unsigned int i = 0; i <= ticks; i++ ) {
		if ( i % plot->getTickLabelInterval() == 0 && i != 0 ) {
			switch ( axis ) {
			case Sx::Math::NX: case Sx::Math::X:
				if ( plot->xAxisVisible() == false ) return true;
				DrawTextAtPoint(Sx::String::ValueOf(curPos) + " [" + units + "]", color, curPos, -0.6f, 0.0f);
			break;
			case Sx::Math::NY: case Sx::Math::Y:
				if ( plot->yAxisVisible() == false ) return true;
				DrawTextAtPoint(Sx::String::ValueOf(curPos) + " [" + units + "] ", color, -1.0f, curPos, 0.0f);
			break;
			case Sx::Math::NZ: case Sx::Math::Z:
				if ( plot->zAxisVisible() == false ) return true;
				DrawTextAtPoint(Sx::String::ValueOf(curPos) + " [" + units + "] ", color, 0.0f, 0.0f, curPos);
			break;
			};
		}
		curPos += (coef * spacing);
	}

	return true;
}

inline bool DrawAxisTicks(Sx::Graphics::Objects::PlotAxes* plot, Sx::Graphics::Representations::Objects::PlotAxes_Representation* physicalRepresentation, Sx::Graphics::Color4f& color) {
	if ( plot == nullptr ) return false;
	if ( physicalRepresentation == nullptr ) return false;

	glColor4fv(color);
	glBegin(GL_LINES);
		unsigned int index = 0;
		if ( plot->xAxisVisible() ) {
			for ( unsigned int i = 0; i <= plot->getXTotalTicks(); i++ ) {
				glVertex3fv(physicalRepresentation->xTickLines[index].data());
				glVertex3fv(physicalRepresentation->xTickLines[index + 1].data());
				index += 2;
			}
		}

		index = 0;
		if ( plot->yAxisVisible() ) {
			for ( unsigned int i = 0; i <= plot->getYTotalTicks(); i++ ) {
				glVertex3fv(physicalRepresentation->yTickLines[index].data());
				glVertex3fv(physicalRepresentation->yTickLines[index + 1].data());
				index += 2;
			}
		}

		index = 0;
		if ( plot->zAxisVisible() ) {
			for ( unsigned int i = 0; i <= plot->getZTotalTicks(); i++ ) {
				glVertex3fv(physicalRepresentation->zTickLines[index].data());
				glVertex3fv(physicalRepresentation->zTickLines[index + 1].data());
				index += 2;
			}
		}
	glEnd();

	return true;
}

inline bool DrawGrid(Sx::Graphics::Objects::PlotAxes* plot, Sx::Graphics::Representations::Objects::PlotAxes_Representation* physicalRepresentation, Sx::Graphics::Color4f& majorColor, Sx::Graphics::Color4f& minorColor, Sx::Math::AxisPlane plane) {
	if ( plot == nullptr ) return false;
	if ( physicalRepresentation == nullptr ) return false;

	unsigned int index = 0;
	unsigned int major = plot->getTickLabelInterval();
	unsigned int nuTicks = plot->getXTicks(false);
	unsigned int puTicks = plot->getXTicks(true);
	unsigned int nvTicks = plot->getYTicks(false);
	unsigned int pvTicks = plot->getYTicks(true);

	if ( plot->gridVisible(Sx::Math::XY) && plane == Sx::Math::XY ) {
		glBegin(GL_LINES);
			for ( unsigned int i = 0; i < (physicalRepresentation->grid_XY.size() / 2); i++ ) {
				IdentifyMajorLines(i, major, majorColor, minorColor, nuTicks, puTicks, nvTicks, pvTicks);
				glVertex3fv(physicalRepresentation->grid_XY[index].data());
				glVertex3fv(physicalRepresentation->grid_XY[index + 1].data());
				index += 2;
			}
		glEnd();
	}

	if ( plot->gridVisible(Sx::Math::YZ) && plane == Sx::Math::YZ ) {
		nuTicks = plot->getYTicks(false);
		puTicks = plot->getYTicks(true);
		nvTicks = plot->getZTicks(false);
		pvTicks = plot->getZTicks(true);

		glBegin(GL_LINES);
			for ( unsigned int i = 0; i < (physicalRepresentation->grid_YZ.size() / 2); i++ ) {
				IdentifyMajorLines(i, major, majorColor, minorColor, nuTicks, puTicks, nvTicks, pvTicks);
				glVertex3fv(physicalRepresentation->grid_YZ[index].data());
				glVertex3fv(physicalRepresentation->grid_YZ[index + 1].data());
				index += 2;
			}
		glEnd();
	}

	if ( plot->gridVisible(Sx::Math::XZ) && plane == Sx::Math::XZ ) {
		nuTicks = plot->getXTicks(false);
		puTicks = plot->getXTicks(true);
		nvTicks = plot->getZTicks(false);
		pvTicks = plot->getZTicks(true);

		glBegin(GL_LINES);
			for ( unsigned int i = 0; i < (physicalRepresentation->grid_XZ.size() / 2); i++ ) {
				IdentifyMajorLines(i, major, majorColor, minorColor, nuTicks, puTicks, nvTicks, pvTicks);
				glVertex3fv(physicalRepresentation->grid_XZ[index].data());
				glVertex3fv(physicalRepresentation->grid_XZ[index + 1].data());
				index += 2;
			}
		glEnd();
	}

	return true;
}

/*
 * PlotAxes may have one or more axes or grids displayed. To handle this select
 * areas of the VBO are rendered.
 */
void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::render(OpenGL_Context* graphicsContext, PlotAxes* plot, PlotAxes_Representation* const physicalRepresentation) {
	
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST); 
	glPushMatrix();
		glMultMatrixf(plot->transformation().toMatrix4f().data());
		DrawAxes(plot, physicalRepresentation);
		DrawGrid(plot, physicalRepresentation, this->majorColor, this->minorColor, Math::XY);
		DrawGrid(plot, physicalRepresentation, this->majorColor, this->minorColor, Math::YZ);
		DrawGrid(plot, physicalRepresentation, this->majorColor, this->minorColor, Math::XZ);
		
		DrawAxisTicks(plot, physicalRepresentation, Color4f(0.0f, 0.0f, 0.0f, 1.0f));
		DrawAxisLabels(plot, Color4f(0.6f, 0.6f, 0.6f, 1.0f), plot->getXTicks(true), Math::X, "s");
		DrawAxisLabels(plot, Color4f(0.6f, 0.6f, 0.6f, 1.0f), plot->getXTicks(false), Math::NX, "");
		DrawAxisLabels(plot, Color4f(0.6f, 0.6f, 0.6f, 1.0f), plot->getYTicks(true), Math::Y, "n");
		DrawAxisLabels(plot, Color4f(0.6f, 0.6f, 0.6f, 1.0f), plot->getYTicks(false), Math::NY, "n");
		DrawAxisLabels(plot, Color4f(0.6f, 0.6f, 0.6f, 1.0f), plot->getYTicks(true), Math::Z, "");
		DrawAxisLabels(plot, Color4f(0.6f, 0.6f, 0.6f, 1.0f), plot->getYTicks(false), Math::NZ, "");

	glPopMatrix();
	glEnable(GL_DEPTH_TEST);

	return;
}

void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::setMajorColor(const Color3f& color) {
	this->majorColor = Color4f(color.getR(), color.getG(), color.getB(), 1.0f);
}

void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::setMajorColor(const Color4f& color) {
	this->majorColor = color;
}

void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::setMajorColor(float r, float g, float b) {
	this->majorColor = Color4f(r, g, b, 1.0f);
}

void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::setMajorColor(float r, float g, float b, float a) {
	this->majorColor = Color4f(r, g, b, a);
}

void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::setMinorColor(const Color3f& color) {
	this->minorColor = Color4f(color.getR(), color.getG(), color.getB(), 1.0f);
}

void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::setMinorColor(const Color4f& color) {
	this->minorColor = color;
}

void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::setMinorColor(float r, float g, float b) {
	this->minorColor = Color4f(r, g, b, 1.0f);
}

void Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::setMinorColor(float r, float g, float b, float a) {
	this->minorColor = Color4f(r, g, b, a);
}

Sx::Graphics::Color4f Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::getMajorColor() const {
	return this->majorColor;
}

Sx::Graphics::Color4f Sx::OpenGL::Graphics::Objects::Rendering::OpenGL_DefaultPlotAxesRenderer::getMinorColor() const {
	return this->minorColor;
}
